Challenge: ccv1

Deep Learning: Was versteckt sich da?¶

Explorative Datenanalyse¶

In [ ]:
%matplotlib inline

import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from PIL import Image, ImageEnhance
import matplotlib.image as mpimg
from tqdm import tqdm

from IPython.display import HTML, display
In [ ]:
# save plots
save_plots = False
path_plots = './plots/'

Erkenntnisse-Übersicht¶

Kurz Übersicht welche Erkenntnisse aus der EDA-Analyse gezogen werden können:

  • Verteilung der Tierklassen auf dem Trainingsdatenset sind relativ ausgewogen und liegen zwischen 10-15%. Mit der Ausnahme von 'hogs' mit 6% Anteilnahme
  • Die Kamera Standorte nehmen sehr unterschiedlich viele Bilder auf, von einzelnen Bildern bis zu mehreren hundert Bildern.
  • Mit Ausnahme des Standorts 'S00060' sind jeweils unterschiedliche Tierarten in den Aufnahmen vertreten
  • Zum Bildformat wurden folgende Probleme festgestellt:
    • Verschiedene Auflösungen sind vorhanden
    • 83 % haben die Auflösung (640x360) oder (960, 540)
    • die restlichen sind zugeschnittene Bilder oder mit einer tiefen Auflösungen
    • Die Bildtiefe ist bei über 85% der Bilder bei 24bit und somit standard RGB, rund 15% sind schwarz-weiss Bilder mit 8Bit
    • Die Bildtiefe kann je Standort unterschiedlich sein
  • Zum Bildinhalt wurden folgende Merkmale festgestellt
    • Viele Bilder mit einem orangen Icon unten links und Datum/Zeit unten rechts vorhanden.
    • Einige Bilder wurden bereits zugeschnitten (um wohl das Icon und Datum zu entfernen)
    • Bei einige Aufnahmen sind die Tiere zu nahe an den Kameras (Bild unscharf oder dunkel)
    • Bilder können überbelichtet sein
    • Bilder können unscharf sein
    • Bilder weisen Artefakte im Bild auf (z.B Standort der Kamera als Text im Bild)

Inhaltsverzeichnis¶

  1. CSV Daten lesen (Train, Test, Label)
  2. Klassifikation Tierarten
  3. Bilddaten Lesen (Train)
    1. Benchmark Ansicht
    2. Random Ansicht
    3. Problematische Bilder
  4. Verteilungen der Tierklassen
  5. Analyse zu Kamera Standorte (site ID)
    1. Verteilung der Bilder je Standort (Train)
    2. Plotten der Bilder je Standort
  6. Analyse zur Bildauflösung
    1. Bilder und Bittiefe
    2. Verteilung Tierklassen, Standort und Bittiefe
  7. Spezifische Kameramerkmale
    1. Bild bearbeitung durch zuschneiden
    2. Überbelichtung von Bildern prüfen
    3. Analysieren von Kamerastandorte
  8. Tests
    1. Überbelichtung prüfen und beheben
    2. Icon entfernen
    3. Crossvalidation

CSV Daten Lesen (Train, Test, Label)¶

Die CSV's beinhalten die Bild ID, Bildpfad, Aufnahmeort ID und die Tierklassifikation (Training)

In [ ]:
train_features = pd.read_csv("../competition_data/train_features.csv", index_col="id")
test_features = pd.read_csv("../competition_data/test_features.csv", index_col="id")
train_labels = pd.read_csv("../competition_data/train_labels.csv", index_col="id")

display(train_features.head())
display(train_labels.head())
filepath site
id
ZJ000000 train_features/ZJ000000.jpg S0120
ZJ000001 train_features/ZJ000001.jpg S0069
ZJ000002 train_features/ZJ000002.jpg S0009
ZJ000003 train_features/ZJ000003.jpg S0008
ZJ000004 train_features/ZJ000004.jpg S0036
antelope_duiker bird blank civet_genet hog leopard monkey_prosimian rodent
id
ZJ000000 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
ZJ000001 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0
ZJ000002 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
ZJ000003 0.0 0.0 0.0 0.0 0.0 0.0 1.0 0.0
ZJ000004 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0
In [ ]:
# reverse One-Hot-Encoding
species_labels = sorted(train_labels.columns.unique())

train_labels_cat = train_labels.copy()
train_labels_cat['label'] = train_labels[species_labels].idxmax(axis=1)
train_labels_cat = train_labels_cat.drop(species_labels, axis=1)

display(train_labels_cat.head())
label
id
ZJ000000 bird
ZJ000001 monkey_prosimian
ZJ000002 bird
ZJ000003 monkey_prosimian
ZJ000004 leopard

Klassifikation Tierarten¶

Klassifikation findet für 8 Tierarten statt

In [ ]:
print(species_labels)
print(f'Anzahl Tierklassen: {len(species_labels)}')
['antelope_duiker', 'bird', 'blank', 'civet_genet', 'hog', 'leopard', 'monkey_prosimian', 'rodent']
Anzahl Tierklassen: 8
In [ ]:
img = mpimg.imread('class_images/class_images.jpg')
plt.imshow(img)
plt.axis('off')
plt.show()

Die Klassen 'bird', 'rodent' umfassen mehrere Tierarten. Die Klasse 'blank' steht für ein Bild ohne ein Tier. Die übrigen Tierklassen sind im obigen Bild enthalten (Tierklassenbilder quelle: google).

Bilddaten Lesen (Train)¶

Probeansicht Bilder (quelle: benchmark file)

In [ ]:
random_state = 42
path_img = '../competition_data/'

# we'll create a grid with 8 positions, one for each label (7 species, plus blanks)
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(20, 20))

# iterate through each species
for species, ax in zip(species_labels, axes.flat):
    # get an image ID for this species
    img_id = (
        train_labels[train_labels.loc[:,species] == 1]
        .sample(1, random_state=random_state)
        .index[0]
    )
    # reads the filepath and returns a numpy array
    img = mpimg.imread(path_img + train_features.loc[img_id].filepath)
    # plot etc
    ax.imshow(img)
    ax.set_title(f"{img_id} | {species}")

Random Ansicht¶

Zelle mehrmals ausführen um unterschiedliche Bilder zu erhalten

In [ ]:
# we'll create a grid with 8 positions, one for each label (7 species, plus blanks)
fig, axes = plt.subplots(nrows=4, ncols=2, figsize=(20, 20))

# iterate through each species
for species, ax in zip(species_labels, axes.flat):
    # get an image ID for this species
    img_id = (
        train_labels[train_labels.loc[:,species] == 1]
        .sample(1)
        .index[0]
    )
    # reads the filepath and returns a numpy array
    img = mpimg.imread(path_img + train_features.loc[img_id].filepath)
    # plot etc
    ax.imshow(img)
    ax.set_title(f"{img_id} | {species}")

Problematische Bilder¶

Folgend wurden spezifisch Bilder herausgesucht um die Problematik von schlechten Bilder zu zeigen.

In [ ]:
example_bad_img = ['ZJ015580', 'ZJ002746', 'ZJ007054', 'ZJ000888', 'ZJ010341', 'ZJ014451',
                   'ZJ004927', 'ZJ007091', 'ZJ013234', 'ZJ013093', 'ZJ004451', 'ZJ010190',
                   'ZJ002138', 'ZJ002196']
example_good_img = ['ZJ003890', 'ZJ004925', 'ZJ012762', 'ZJ014396', 'ZJ015264', 'ZJ000895',
                    'ZJ007334', 'ZJ004978', 'ZJ014157', 'ZJ010885']


def plot_image_from_image_id(image_ids: list, nrows=4, ncols=3, figsize=(15, 15),
                             fontsize=10, path='../competition_data/'):
    # create grid 
    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)

    # iterate through each bad image
    for idx, (img_id, ax) in enumerate(zip(image_ids, axes.flat)):
        # get image label
        img_label = train_labels_cat.loc[image_ids[idx]]['label']
        # reads the filepath and returns a numpy array
        img = mpimg.imread(path + train_features.loc[img_id].filepath)
        # image dimension
        width, height = Image.open(path_img + train_features.loc[img_id].filepath).size
        # plot etc
        ax.imshow(img)
        ax.set_title(f"{img_id} | {img_label} | {str(width)}x{str(height)}", fontsize=fontsize)

plot_image_from_image_id(example_bad_img)
#plot_image_from_image_id(example_good_img)

Folgende Probleme zum Bildinhalt wurden bemerkt:

  • Viele Bilder mit einem orangen Icon unten links und Datum/Zeit unten rechts vorhanden.
  • Einige Bilder wurden bereits zugeschnitten um wohl das Icon und Datum zu entfernen.
  • Bei einige Aufnahmen sind die Tiere zu nahe an den Kameras (daher is das Bild unscharf oder dunkel)
  • Bilder können überbelichtet sein
  • Bilder können unscharf sein
  • Bilder weisen Artefakte im Bild auf (z.B Standort der Kamera als Text im Bild)

Zu Prüfen: wenn die Bilder im Notebook mit den Bilder im File Ordner verglichen werden, fällt auf dass die Bilder in der Windowsansicht schwarz-weiss im Notebook jedoch blau-gelb angezeigt werden. Beispiel 'ZJ003494'.
Interpretation von mpimg prüfen.

Verteilungen der Tierklassen¶

(Quelle: Benchmark) Wie im Benchmark Notebook beschrieben, entspricht die Tierklassen Verteilungen nicht dem eigentlichen Vorkommen. Die (Trainings)daten wurden für die Competition bereits vorbereitet.

In [ ]:
display(train_labels.sum().sort_values(ascending=False))
display(train_labels.sum().divide(train_labels.shape[0]).sort_values(ascending=False))
monkey_prosimian    2492.0
antelope_duiker     2474.0
civet_genet         2423.0
leopard             2254.0
blank               2213.0
rodent              2013.0
bird                1641.0
hog                  978.0
dtype: float64
monkey_prosimian    0.151140
antelope_duiker     0.150049
civet_genet         0.146955
leopard             0.136705
blank               0.134219
rodent              0.122089
bird                0.099527
hog                 0.059316
dtype: float64

Analyse zu Kamera Standorte (site ID)¶

In [ ]:
train_features.head(2)
Out[ ]:
filepath site
id
ZJ000000 train_features/ZJ000000.jpg S0120
ZJ000001 train_features/ZJ000001.jpg S0069
In [ ]:
print(f'Anzahl Standort Training-Kameras: {len(train_features.site.unique())}')  
print(f'Anzahl Standort Test-Kameras: {len(test_features.site.unique())}')

print(f'Mittelwert: {len(train_features.site) / len(train_features.site.unique()):.0f} Bilder pro Ort, Train')
print(f'Mittelwert: {len(test_features.site) / len(test_features.site.unique()):.0f} Bilder pro Ort, Test')
Anzahl Standort Training-Kameras: 148
Anzahl Standort Test-Kameras: 51
Mittelwert: 111 Bilder pro Ort, Train
Mittelwert: 88 Bilder pro Ort, Test

Verteilung der Bilder je Standort (Train)¶

In [ ]:
df_site_count = train_features.groupby('site').count().reset_index().sort_values('filepath', ascending=True)

# plot histgram
plt.figure(figsize=(20, 8))
plt.bar(df_site_count.site, df_site_count.filepath)
plt.title('Anzahl Bilder je Kamera Standort (Trainset)', fontsize=20)
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90, fontsize=8)
plt.yticks(fontsize=12)
plt.grid(axis='y')
if save_plots:
    plt.savefig(path_plots + 'number_images_site.png', bbox_inches="tight")
plt.show()

Insgesamt bestehen 148 verschiedene Kamera Standorte. Die ID-Nummerierung verläuft von S0001 bis S0198 (ID-Nummerierung nicht komplet durch numeriert). Die Verteilung zeigt die Anzahl Bilder die für einen Standort zur Verfügung stehen. Einige Standorte nehmen nur sehr wenige Bilder auf (1-2) ander enthalten hunderte Bilder.

Verteilung der Tierklassen je Standort¶

In [ ]:
df_train_feature_labels = train_features.merge(train_labels_cat, left_index=True, right_index=True)
df_train_feature_labels.head()
Out[ ]:
filepath site label
id
ZJ000000 train_features/ZJ000000.jpg S0120 bird
ZJ000001 train_features/ZJ000001.jpg S0069 monkey_prosimian
ZJ000002 train_features/ZJ000002.jpg S0009 bird
ZJ000003 train_features/ZJ000003.jpg S0008 monkey_prosimian
ZJ000004 train_features/ZJ000004.jpg S0036 leopard
In [ ]:
df_stacked_plot = pd.crosstab(df_train_feature_labels['site'], df_train_feature_labels['label'])
df_stacked_plot['max_count'] = df_stacked_plot.sum(axis=1)
df_stacked_plot = df_stacked_plot.sort_values('max_count', ascending=True)
df_stacked_plot.head()
Out[ ]:
label antelope_duiker bird blank civet_genet hog leopard monkey_prosimian rodent max_count
site
S0102 0 0 0 0 0 0 1 0 1
S0078 0 0 2 0 0 0 0 0 2
S0079 0 0 2 0 0 0 0 0 2
S0178 0 0 0 0 0 2 0 0 2
S0143 1 0 2 0 0 0 0 0 3
In [ ]:
def plot_stacked_classes_site(stacked_plot, min_count = 50, save_plot=False):
    stacked_plot = stacked_plot[stacked_plot['max_count'] > min_count]
    stacked_plot = stacked_plot.drop(columns='max_count')


    stacked_plot.plot(kind='bar', stacked=True, figsize=(20,8))
    plt.title('Vorkommen Tierklassen je Standort (Train)', fontsize=20)
    plt.xlabel('site ID')
    plt.ylabel('counts')
    plt.xticks(rotation=90)
    plt.grid(axis='y')
    if save_plots:
        plt.savefig(path_plots + 'dist_animals_n_images_site.png', bbox_inches="tight")
    plt.show()

plot_stacked_classes_site(df_stacked_plot, 50, save_plots)

Plotten von Bildern je Standort¶

In [ ]:
site = 'S0196'
len(train_features[train_features['site'] == site])
Out[ ]:
15
In [ ]:
def plot_image_from_site_id(site_id: str, nrows=4, ncols=3, figsize=(15, 15), 
                            path='../competition_data/'):
    # get images from site
    img_site = train_features[train_features['site'] == site_id].reset_index()
    img_site = img_site[0:nrows*ncols]
    # create grid 
    fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=figsize)

    # iterate through each bad image
    for img_id, image_path, ax in zip(img_site.id, img_site.filepath, axes.flat):
        # reads the filepath and returns a numpy array
        img = mpimg.imread(path + image_path)
         # get image label
        img_label = train_labels_cat.loc[img_id]['label']
        # plot
        ax.imshow(img)
        ax.set_title(f"{img_id} | {img_label}")

plot_image_from_site_id(site)

Analyse zu Bildauflösung¶

Hier soll geprüft werden wie stark die Auflösungen der Kameras varrieren.

Die Auflösungen kann mit 'mpimg' durch shape wiedergegeben werden. Eine schneller Möglichkeit ist 'Pillow' zu verwenden, die Bittiefe muss jedoch seperate mit getbands() ausgelesen werden.

In [ ]:
mode_to_bpp = {'1':1, 'L':8, 'P':8, 'RGB':24, 'RGBA':32, 'CMYK':32, 'YCbCr':24, 'I':32, 'F':32}

img_id = []
img_width = []
img_height = []
img_dim = []
band_mode = []
bit_depth = []

for imag_id in train_features.reset_index().id:
    # get id and dimensions
    img = Image.open(path_img + train_features.loc[imag_id].filepath)
    width, height = img.size
    dim = img.size
    mode = img.getbands()

    # save to list
    img_id.append(imag_id)
    img_width.append(width)
    img_height.append(height)
    img_dim.append(dim)
    band_mode.append(mode)

df_img_shape_pil = pd.DataFrame({'id': img_id, 'width': img_width, 'height': img_height, 
                                 'dim':img_dim, 'band_mode': band_mode})

df_img_shape_pil.head(2)
Out[ ]:
id width height dim band_mode
0 ZJ000000 960 540 (960, 540) (R, G, B)
1 ZJ000001 960 540 (960, 540) (R, G, B)
In [ ]:
unique_dim = df_img_shape_pil.dim.value_counts().reset_index().sort_values('dim', ascending=False)
unique_dim = unique_dim.rename(columns={'dim': 'count', 'index': 'dim'})
unique_dim['count_relativ'] = np.round(unique_dim['count'] / sum(unique_dim['count']), 2)
display(unique_dim.head())
dim count count_relativ
0 (640, 360) 7490 0.45
1 (960, 540) 6345 0.38
2 (640, 335) 970 0.06
3 (360, 240) 864 0.05
4 (960, 515) 458 0.03
In [ ]:
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
fontsize = 8

ax[0].bar(np.arange(len(unique_dim)), unique_dim['count'])
ax[0].set_xticks(np.arange(len(unique_dim)), unique_dim.dim)
ax[0].set_title('Verteilung der Bildauflösungen')
ax[0].set_ylabel('counts', fontsize=fontsize)
ax[0].tick_params(axis='y', labelsize=fontsize)
ax[0].set_xticklabels(unique_dim.dim, rotation=60)

for i, v in enumerate(unique_dim['count']):
    ax[0].text(i, v, str(v), ha='center', va='bottom', fontsize=fontsize)

ax[1].bar(np.arange(len(unique_dim)), unique_dim['count_relativ'] )
ax[1].set_xticks(np.arange(len(unique_dim)), unique_dim.dim)
ax[1].set_title('Verteilung der Bildauflösungen [relativ]')
ax[1].set_ylabel('counts', fontsize=fontsize)
ax[1].tick_params(axis='y', labelsize=fontsize)
ax[1].set_xticklabels(unique_dim.dim, rotation=60)


for i, v in enumerate(unique_dim['count_relativ']):
    ax[1].text(i, v, str(v*100)+'%', ha='center', va='bottom', fontsize=fontsize)
    
if save_plots:
    plt.savefig(path_plots + 'dist_image_resolution.png', bbox_inches="tight")
plt.show()

Über 80% der Bilder haben die Auflösung (640, 360) oder (960, 540)

Bilder und Bittiefe¶

Die Bittiefe beschreibt wie viele Bits zur Darstellung von Farben eines Pixels zur Verfügung stehen. Standard ist 24Bit, 8Bit für jeden Farbkanal von RGB.

In [ ]:
df_bit_depth = df_img_shape_pil['band_mode'].value_counts().reset_index(name='count')
df_bit_depth = df_bit_depth.rename(columns={'index':'mode'})
df_bit_depth['count_rel'] = df_bit_depth['count'] / df_bit_depth['count'].sum()
df_bit_depth
Out[ ]:
mode count count_rel
0 (R, G, B) 14291 0.866752
1 (L,) 2197 0.133248
In [ ]:
df_bit_depth = df_img_shape_pil['band_mode'].value_counts().reset_index(name='count')
df_bit_depth = df_bit_depth.rename(columns={'index':'mode'})
# add relativ count
df_bit_depth['count_rel'] = df_bit_depth['count'] / df_bit_depth['count'].sum()
df_bit_depth.plot(x='mode', y='count_rel', kind='bar', figsize=(6,4))
plt.suptitle('Verteilung Bit Tiefe Bilder', fontsize=12)
plt.title('RGB: 24bit, L: 8bit', fontsize=8)
plt.xlabel('band mode')
plt.ylabel('count relativ')
plt.xticks(rotation=0)
plt.grid()
if save_plots:
    plt.savefig(path_plots + 'dist_image_depth.png', bbox_inches="tight")
plt.show()

Über 80% der Bilder haben 24Bit Farbtiefe (RGB), die restlichen bestehen aus 8Bit (L) für monochrom oder schwarz-weiss Bilder.

Bittiefe je Kamerastandort¶

In [ ]:
df_train_feature_labels_dim = df_train_feature_labels.merge(df_img_shape_pil[['id', 'dim', 'band_mode']], left_index=True, right_on='id')
df_train_feature_labels_dim = df_train_feature_labels_dim.set_index('id')
df_train_feature_labels_dim['dim'] = df_train_feature_labels_dim['dim'].astype(str)
df_train_feature_labels_dim['band_mode'] = df_train_feature_labels_dim['band_mode'].astype(str)
df_train_feature_labels_dim.head()
Out[ ]:
filepath site label dim band_mode
id
ZJ000000 train_features/ZJ000000.jpg S0120 bird (960, 540) ('R', 'G', 'B')
ZJ000001 train_features/ZJ000001.jpg S0069 monkey_prosimian (960, 540) ('R', 'G', 'B')
ZJ000002 train_features/ZJ000002.jpg S0009 bird (640, 360) ('R', 'G', 'B')
ZJ000003 train_features/ZJ000003.jpg S0008 monkey_prosimian (640, 360) ('R', 'G', 'B')
ZJ000004 train_features/ZJ000004.jpg S0036 leopard (640, 335) ('R', 'G', 'B')
In [ ]:
df_stacked_plot_bit = pd.crosstab(df_train_feature_labels_dim['site'], df_train_feature_labels_dim['band_mode'])

# sort values
df_stacked_plot_bit['max_count'] = df_stacked_plot_bit.sum(axis=1)
df_stacked_plot_bit = df_stacked_plot_bit.sort_values('max_count', ascending=True)
df_stacked_plot_bit
Out[ ]:
band_mode ('L',) ('R', 'G', 'B') max_count
site
S0102 0 1 1
S0078 0 2 2
S0079 0 2 2
S0178 0 2 2
S0143 0 3 3
... ... ... ...
S0036 0 456 456
S0008 0 541 541
S0063 0 557 557
S0009 0 664 664
S0060 0 1132 1132

148 rows × 3 columns

In [ ]:
min_count = 0
stacked_plot_bit = df_stacked_plot_bit[df_stacked_plot_bit['max_count'] >= min_count].copy()
stacked_plot_bit = stacked_plot_bit.drop(columns='max_count')
print(len(stacked_plot_bit))

stacked_plot_bit.plot(kind='bar', stacked=True, figsize=(20,6))
plt.title('Bild Bittiefe je Standort (Train)', fontsize=20)
plt.xlabel('site ID')
plt.ylabel('counts')
plt.xticks(rotation=90, fontsize=8)
plt.grid(axis='y')
if save_plots:
    plt.savefig(path_plots + 'image_depth_site.png', bbox_inches="tight")
plt.show()
148

Überraschen ist dass die Kamerastandort unterschiedliche Bittiefen haben (Beispiel S0014). Es wurden evtl unterschiedliche Kameras pro Standort verwendet oder die Nachtaufnahmen werden in schwarz-weiss erstellt.

Verteilung Tierklassen Biettiefe und Site¶

Hier soll untersucht werden ob die schwarz-weiss Aufnahmen von den Tierklassen abhängig sind.

In [ ]:
df_train_feature_labels_dim.head(2)
Out[ ]:
filepath site label dim band_mode
id
ZJ000000 train_features/ZJ000000.jpg S0120 bird (960, 540) ('R', 'G', 'B')
ZJ000001 train_features/ZJ000001.jpg S0069 monkey_prosimian (960, 540) ('R', 'G', 'B')
In [ ]:
for label in species_labels:    
    df_train_feature_labels_dim_x = df_train_feature_labels_dim[df_train_feature_labels_dim['label'] == label]

    df_stacked_plot_bit = pd.crosstab(df_train_feature_labels_dim_x['site'], df_train_feature_labels_dim_x['band_mode'])

    # sort values
    df_stacked_plot_bit['max_count'] = df_stacked_plot_bit.sum(axis=1)
    df_stacked_plot_bit = df_stacked_plot_bit.sort_values('max_count', ascending=True)
    df_stacked_plot_bit.tail()

    # filter values
    min_count = 0
    stacked_plot_bit = df_stacked_plot_bit[df_stacked_plot_bit['max_count'] >= min_count].copy()
    stacked_plot_bit = stacked_plot_bit.drop(columns='max_count')

    # plot 
    stacked_plot_bit.plot(kind='bar', stacked=True, figsize=(20,5))
    plt.title(f'Bild Bittiefe je Standort: {label} (Trainset)', fontsize=20)
    plt.xlabel('site ID')
    plt.ylabel('counts')
    plt.xticks(rotation=90)
    plt.grid(axis='y')
    if save_plots:
        plt.savefig(f'{path_plots}dist_bitdepth_site_{label}.png', bbox_inches="tight")
    plt.tight_layout()
    plt.show()

Es scheint dass viele der monochrom Bilder ohne enthaltene Tiere erstellt werden, Klasse 'blank'. Ein Muster für Nachtaktive Tiere kann nicht direkt abgeleitet werden. Festzustellen ist dass die Tierklassen, relative gut verteilt, an vielen Kamera Standorte erfasst werden.

Spezifische Kameramerkmale¶

Am unteren Rand haben viele Bilder das gleiche Logo und Zeitstempfel. Auf verschiedenen Bildern wurden diese teilweise durch beschneiden entfernt. Hier soll untersucht werden:

  • ob Aussagen über die Bearbeitung gemacht werden können (z.B. unterer Teil entfert)?
  • kann durch die Auflösung auf eine Bearbeitung geschlossen werden?
  • habe Standort bestimmte Eigenschaft?

Bild bearbeitung durch zuschneiden¶

In [ ]:
# images with Logo and time:
img_logo_time = ['ZJ000001', 'ZJ000002', 'ZJ000003', 'ZJ000005',
                 'ZJ000006', 'ZJ000025', 'ZJ000026', 'ZJ000031', 'ZJ000140']


plot_image_from_image_id(img_logo_time, nrows=3, figsize=(10, 7), fontsize=8)

Die neun Testbilder gehören alle zu beiden Hauptgruppen der Bildauflösungen, (640x360) und (960x540)

In [ ]:
# images cut
img_logo_time_cut = ['ZJ000004', 'ZJ000053', 'ZJ000063', 'ZJ000090',
                 'ZJ000099', 'ZJ000106', 'ZJ000114', 'ZJ000119', 'ZJ000156']

plot_image_from_image_id(img_logo_time_cut, nrows=3, figsize=(10, 7), fontsize=8)

Die neun Testbilder, bei denen das Logo und Zeitstempfel teilweise entfernt wurde, haben jeweils eine Beschneidung in der Dimension 'height'. Daraus könnte geschlossen werden dass die bearbeiteten Bilder der Hauptgruppe angehören jedoch mit fehlendem unterem Abschnitt, (640x360) -> (640x335) und (960x540) -> (960x515)

In [ ]:
# special cases
img_special_case = ['ZJ000019', 'ZJ000015', 'ZJ000016', 'ZJ000018',
                 'ZJ000065', 'ZJ000124', 'ZJ000132', 'ZJ000139', 'ZJ000142']

plot_image_from_image_id(img_special_case, nrows=3, figsize=(10, 7), fontsize=8)

Überbelichtung bei Bilder prüfen¶

In [ ]:
def is_overexposed(image_path, threshold=220):
    '''
    high threshold for high brightness
    '''
    # Öffnen des Bildes mit Pillow
    image = Image.open(image_path)

    # Berechnung der durchschnittlichen Helligkeit des Bildes
    brightness = sum(image.convert('L').getdata()) / (image.width * image.height)

    # Überprüfung, ob die Helligkeit über dem Schwellenwert liegt
    return brightness > threshold
In [ ]:
images_overexposed = {}

for im_path in train_features['filepath'].head(100):
    path = path_img + im_path

    # check image is overexposed
    if is_overexposed(path, threshold=220):
        image_name = os.path.split(path)[1].split('.')[0]
        images_overexposed[image_name] = im_path

print(f'Found {len(images_overexposed)} Images')

# plot images
images_overexposed_ids = list(images_overexposed.keys())
plot_image_from_image_id(images_overexposed_ids, nrows=1, figsize=(10, 7), fontsize=8)
Found 3 Images

Analysieren von Kamerastandorte¶

In [ ]:
df_site_count[df_site_count['filepath'] > 400] 
Out[ ]:
site filepath
88 S0120 423
32 S0038 429
45 S0059 438
34 S0043 444
31 S0036 456
7 S0008 541
49 S0063 557
8 S0009 664
46 S0060 1132
In [ ]:
plot_image_from_site_id('S0106')

Tests¶

Test: Anpassungen von Überbelichteten Bildern¶

In [ ]:
list(images_overexposed.keys())
Out[ ]:
['ZJ000007', 'ZJ000059', 'ZJ000084']
In [ ]:
for image_id, path in images_overexposed.items():
    path = path_img + path
    img = Image.open(path)
    img.save(f"./enhanced_images/{image_id}_pre_enh.jpg")
    enhancer = ImageEnhance.Brightness(img)
    # to reduce brightness by 50%, use factor 0.5
    img = enhancer.enhance(0.5)

    img.save(f"./enhanced_images/{image_id}_post_enh.jpg")

Test Icon entfernen¶

Hier soll versucht werden ob das Logo im linken unteren Teil des Bildes automatisch erkannt werden kann

In [ ]:
# crop image
def crop_image_for_logo(pillow_image, plot_image=False):
    # dim img
    width, height = img.size

    # crop images, left corner
    crop_height = height * 0.09
    crop_width = width * 0.95

    x1 = 0
    y1 = height - crop_height
    x2 = width - crop_width
    y2 = height

    cropped_img = img.crop((x1, y1, x2, y2))

    if plot_image:
        plt.imshow(cropped_img)
        plt.show()

    return cropped_img

# get image main color
def get_img_main_color(pillow_imag, print_info=False):
    # Wandle das Bild in den RGB-Modus um, falls es noch nicht im RGB-Modus vorliegt.
    if pillow_imag.mode != "RGB":
        pillow_imag = pillow_imag.convert("RGB")

    # Extrahiere die Farb-Kanäle aus dem Bild.
    r, g, b = pillow_imag.split()[0], pillow_imag.split()[1], pillow_imag.split()[2]

    # Berechne den durchschnittlichen Wert des Kanals.
    mean_r = sum(r.getdata()) / len(r.getdata())
    mean_g = sum(g.getdata()) / len(g.getdata())
    mean_b = sum(b.getdata()) / len(b.getdata())

    mean_rgb = {'red': mean_r, 'green': mean_g, 'blue': mean_b}

    max_mean_color = max(mean_rgb, key=lambda x:mean_rgb[x])
    if print_info:
        print(f'image is mostly: {max_mean_color}')
        return
    
    return max_mean_color
In [ ]:
# read image
img = Image.open(path_img + train_features.loc['ZJ000001'].filepath)

# crop image
cropped_image = crop_image_for_logo(img, plot_image=True)

# get main color image
get_img_main_color(cropped_image, print_info=True)
image is mostly: red

Cross-validation¶

In [ ]: